<?php
if (!defined('CX')) {
    exit();
}

/**
 *
 *
 * @author 星期八 <ixqbar@gmail.com>
 * @editor $Author: ixqbar $
 * @date $Date: 2011-05-07 13:24:30 +0000 (Sat, 07 May 2011) $
 * @version $Revision: 3 $
 */

final class CXTool {
    static $_application_config;
    static $import_class;

    /**
     * get config from application_config
     * @param string $key
     * @return array/string
     */
    public static function getConfig($key) {
        if (!isset(self::$_application_config[$key])) {
            $application_config = include APPLICATION_PATH . '/library/config/config.php';
            $keys = explode(':', $key);
            $keys_length = count($keys);
            if ($keys_length > 1) {
                $temp_application_config = $application_config;
                $index = 0;
                do {
                    if (isset($temp_application_config[$keys[$index]])) {
                        $key_value = $temp_application_config[$keys[$index]];
                        $temp_application_config = $key_value;
                    } else {
                        throw new Exception("cant find config $key in application config");
                    }
                    $index++;
                } while ($index < $keys_length);
                unset($temp_application_config, $index);
                self::$_application_config[$key] = $key_value;
            }
            else {
                self::$_application_config[$key] = $application_config[$key];
            }
        }
        
        return self::$_application_config[$key];
    }

    /**
     * wget
     * @param string $url
     * @param array $post_var
     * @return string
     */
    public static function wget($url, $post_var = array()) {
        $result = '';
        if (empty($url) || !function_exists('curl_init')) {
            throw new Exception('cant find extension curl');
        }

        $ch = curl_init();
        $ch_option = array(
            CURLOPT_URL     => $url,
            CURLOPT_HEADER  => 0,
            CURLOPT_RETURNTRANSFER => 1
        );
        if ($post_var && is_array($post_var)) {
            $ch_option[CURLOPT_POST] = 1;
            $ch_option[CURLOPT_POSTFIELDS] = $post_var;
        }
        curl_setopt_array($ch, $ch_option);
        $result = curl_exec($ch);
        curl_close($ch);

        return $result;
    }

    public static function getTime() {
        return time();
    }

    /**
     *
     * @param string $class  like @.news
     */
    public static function import($class) {
        if (empty($class)) {
            throw new Exception('import must pass params');
        }
        
        if (self::$import_class[$class]) {
            return self::$import_class[$class];
        }
        else {
            list($path_type, $class_name) = explode('.', $class, 2);
            try {
                $monitor = CXMonitor::instance();
                switch ($path_type) {
                    case '@':
                        //module
                        require_once APPLICATION_MODULE_PATH . '/' . $class_name . '.module.php';
                        $class_name = ucfirst($class_name) . 'Module';
                        $monitor->record($class_name . '_construct');
                        return new $class_name;
                        break;
                }
            } catch (Exception $e) {
                throw $e;
            }
        }
    }

    public static function error($errno, $errstr, $errfile, $errline) {
        echo $errstr;
    }

    public static function exception(Exception $e) {
        echo $e->getMessage();
    }

    public static function halt() {
        $monitor = CXMonitor::instance();
        if (200 == $monitor->http_code && self::getConfig('debug')) {
            var_dump($monitor->debug_info);
        }
    }
}


/**
 * Template
 */
final class CXTemplate {
    static $instance;
    public $monitor;

    private function  __construct() {
        $this->monitor = CXMonitor::instance();
        $this->monitor->record('CXTemplate_construct');
        
        $template_config = CXTool::getConfig('template');
        foreach ($template_config as $property => $value) {
            $this->$property = $value;
        }
    }

    public static function instence() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }

    /**
     * 
     * @param int $page
     * @return boolean $cache
     */
    public function cached($page) {
        $cache = false;

        $now_time = CXTool::getTime();
        $expire_seconds = $this->cache_lifetime;
        $page_cache_content = '';
        
        $last_expire = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : 0;
        $last_etag = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : 0;
        if ((strtotime($last_expire) + $expire_seconds) > $now_time) {
            header('HTTP/1.1 304');
            $cache = true;
        } elseif ($last_etag) {
            $page_cache_content = $this->getPageCacheContent($page);
            if ($page_cache_content && $last_etag == $page_cache_content['etag']) {
                header('HTTP/1.1 304');
                $cache = true;
            }
        }

        if (false == $cache) {
            if ('' == $page_cache_content) {
                $page_cache_content = $this->getPageCacheContent($page);
                if ($page_cache_content && $page_cache_content['expire'] <= $now_time) {
                    $page_cache_content = '';
                    unlink($this->getPageCachePath($page));
                }
            }
            
            if ($page_cache_content) {
                header('Last-Modified: ' . gmdate('D, d M y H:i:s', $page_cache_content['modify']) . ' GMT');
                header('Expires: ' . gmdate('D, d M y H:i:s', $page_cache_content['expire']) . ' GMT');
                header('Cache-Control: max-age=' . $page_cache_content['seconds']);
                header('ETag: ' . $page_cache_content['etag']);
                $cache = true;
                echo $page_cache_content['content'];
            } else {
                header('Last-Modified: ' . gmdate('D, d M y H:i:s', $now_time) . ' GMT');
                header('Expires: ' . gmdate('D, d M y H:i:s', $now_time + $expire_seconds) . ' GMT');
                header('Cache-Control: max-age=' . $expire_seconds);
                header('ETag: ' . $this->getEtag($page));
            }
        } else {
            $this->monitor->http_code = 304;
        }

        return $cache;
    }

    /**
     *
     * @param string $page
     * @return string 
     */
    private function getPagePath($page) {
        $page_path = APPLICATION_HTDOC_PATH . '/template/' . $this->style . '/' . $page . $this->page_suffix;
        if (!file_exists($page_path)) {
            throw new Exception($page_path . ' not exist');
        }

        return $page_path;
    }

    /**
     *
     * @param string $page
     * @return string
     */
    private function getPageCachePath($page) {
        return APPLICATION_HTDOC_PATH . '/temp/' . md5($page . $this->page_suffix) . '.cache';
    }

    /**
     *
     * @param string $page
     * @param string $content
     */
    private function getPageCacheContent($page) {
        $page_cache_content = array();
        $page_cache_path = $this->getPageCachePath($page);
        if (file_exists($page_cache_path)) {
            $page_cache_content = include $page_cache_path;
        }

        return $page_cache_content;
    }

    /**
     *
     * @param string $page
     * @param array $content
     */
    private function setPageCacheContent($page, $content) {
        $final_content = "<?php\n";
        $final_content .= "return " . var_export($content, true) . ";\n";
        $final_content .= "?>";
        
        file_put_contents($this->getPageCachePath($page), $final_content);
    }

    /**
     *
     * @param string $page
     * @return string
     */
    private function getEtag($page) {
        return md5($page . $this->page_suffix . $this->cache_lifetime);
    }
    
    /**
     *
     * @param string $page
     * @param array $data
     * @param boolean $cache
     */
    public function render($page, $data = array(), $caching = '') {
        $page_path = $this->getPagePath($page);

        //set variable
        if ($data) {
            foreach ($data as $variable => $value) {
                $$variable = $value;
            }
        }
        
        $now_time = CXTool::getTime();
        $cache_content = array(
            'expire' => $now_time + $this->cache_lifetime,
            'seconds'=> $this->cache_lifetime,
            'etag'   => $this->getEtag($page),
            'page'   => $page . $this->page_suffix,
            'create' => $now_time,
            'modify' => $now_time
        );
        
        //get page content
        ob_start();
        include $page_path;
        $cache_content['content'] = ob_get_contents();
        ob_end_flush();

        $caching = '' == $caching ? $this->caching : $caching;
        if ($caching) {
            $this->setPageCacheContent($page, $cache_content);
        }
    }

}

/**
 * Log
 */
final class CXLog {

    /**
     *
     * @param string $message
     * @param string $type
     */
    public static function record($message, $type = '') {
        $result = '';

        if (empty($message)) {
            return $result;
        }

        $message = is_scalar($message) ? $message : json_encode($message);
        switch ($type) {
            case 'xhprof':
                if (CXTool::getConfig('services:log:xhprof_spost')) {
                    $result = CXTool::wget(CXTool::getConfig('services:log:xhprof_server') . '?' . $type . '=' . $message);
                }
                break;
            default:
        }
        
        return $result;
    }
}

/**
 * Cache
 */
class CXCache {
    static $cache;

    /**
     *
     * @param string $name
     * @return Redis
     */
    private static function getCache($name) {
        $redis_handle = null;
        switch ($name) {
            case 'server':
                $redis_config = CXTool::getConfig('cacheserver:server');
                $redis_handle = new Redis();
                $redis_handle->connect($redis_config['host'], $redis_config['port'], $redis_config['timeout']);
                $redis_handle->select($redis_config['database']);
                break;
            case 'worker':
                $redis_config = CXTool::getConfig('cacheserver:server');
                $redis_handle = new Redis();
                $redis_handle->connect($redis_config['host'], $redis_config['port'], $redis_config['timeout']);
                $redis_handle->select($redis_config['database']);
                break;
        }

        $redis_handle->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
        $mointor = CXMonitor::instance();
        $monitor->record('CXCache_construct');

        return $redis_handle;
    }

    /**
     *
     * @param string $name
     * @return Redis
     */
    public static function instance($name = 'server') {
        try {
            if (!isset(self::$cache[$name]) || !self::$cache[$name]->ping()) {
                self::$cache[$name] = self::getCache($name);
            }
        } catch (Exception $e) {
            self::$cache[$name] = self::getCache($name);
        }

        return self::$cache[$name];
    }

}


/**
 * DB
 */
class CXDB {
    static $instance;
    private $_rdb_handle;
    private $_wdb_handle;
    public $monitor;

    private function  __construct() {
        $this->monitor = CXMonitor::instance();
        $this->monitor->record('CXDB_construct');
    }

    private function  __clone() {}

    public static function instance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    private function connect($master = true) {
        if ($master) {
            if ($this->_wdb_handle && $this->_wdb_handle->ping()) {
                return true;
            } else {
                $db_config = CXTool::getConfig('dbserver');
                $this->_wdb_handle = new mysqli($db_config['master']['server'], $db_config['master']['username'], $db_config['master']['password'], $db_config['master']['database']);
                if ($this->_wdb_handle->connect_error) {
                    die("Mysql Connect Error : " . $this->_wdb_handle->connect_error);
                }
                $this->_wdb_handle->set_charset($db_config['master']['charset']);
            }
        } else {
            if ($this->_rdb_handle && $this->_rdb_handle->ping()) {
                return true;
            } else {
                $db_config = CXTool::getConfig('dbserver');
                if ($db_config['independent']) {
                    $this->_rdb_handle = new mysqli($db_config['salve']['server'], $db_config['salve']['username'], $db_config['salve']['password'], $db_config['salve']['database']);
                    if ($this->_rdb_handle->connect_error) {
                        die("Mysql Connect Error : " . $this->_rdb_handle->connect_error);
                    }
                    $this->_rdb_handle->set_charset($db_config['salve']['charset']);
                } else {
                    if (!$this->_wdb_handle || !$this->_wdb_handle->ping()) {
                        $this->_wdb_handle = new mysqli($db_config['master']['server'], $db_config['master']['username'], $db_config['master']['password'], $db_config['master']['database']);
                        if ($this->_wdb_handle->connect_error) {
                            die("Mysql Connect Error : " . $this->_wdb_handle->connect_error);
                        }
                        $this->_wdb_handle->set_charset($db_config['master']['charset']);
                    }
                    $this->_rdb_handle = $this->_wdb_handle;
                }
            }
        }
    }

    /**
     *
     * @param string $sql
     * @return string/array
     */
    public function select($sql) {
        $rows = array();
        
        $this->monitor->record($sql, 'sql');
        $this->connect(false);
        $result = $this->_wdb_handle->query($sql);
        if ($result) {
            while($row = $result->fetch_array(MYSQLI_ASSOC)) {
                $rows[] = $row;
            }
        }

        return $rows;
    }

    /**
     *
     * @param string $sql
     * @return int
     */
    public function update($sql) {
        $this->monitor->record($sql, 'sql');
        $this->connect(true);
        $this->_wdb_handle->query($sql, MYSQLI_USE_RESULT);
        return $this->_wdb_handle->affected_rows;
    }

    public function get_last_id() {
        return $this->_wdb_handle->insert_id;
    }

    /**
     *
     * @param string $sql
     * @return int
     */
    public function delete($sql) {
        $this->monitor->record($sql, 'sql');
        $this->connect(true);
        $this->_wdb_handle->query($sql, MYSQLI_USE_RESULT);
        return $this->_wdb_handle->affected_rows;
    }
}

/**
 * Monitor
 */
final class CXMonitor {
    static $instance;
    public $debug_info = array();
    public $http_code = 200;
    private $_temp = array();

    /**
     *
     * @param string $page
     */
    private function  __construct() {
        $this->record('CXMonitor_construct');
        $this->_temp['xhprof'] = function_exists('xhprof_disable') && CXTool::getConfig('xhprof');
    }

    private function  __clone() {}

    /**
     *
     * @return Monitor
     */
    public static function instance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public function before() {
        $this->_temp['start_time'] = microtime(true);
        if ($this->_temp['xhprof']) {
            xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
        }

    }

    /**
     * 记录页面耗时
     */
    public function after() {
        $this->record('run:' . (microtime(true) - $this->_temp['start_time']));
        if ($this->_temp['xhprof']) {
            $this->record(xhprof_disable(), 'xhprof');
        }
    }

    /**
     * 
     * @param string $message
     * @param string $type 
     */
    public function record($message, $type = 'runlog') {
        $this->debug_info[$type][] = array(CXTool::getTime('[Y-m-d,H:i:s]'), $message);
    }
}


/**
 * Worker
 */
final class CXWorker {
    static $redis_handle;
    
    public static function enwork(/*args like "news.filter", 111*/) {
        $queue_name = 'worker';
        if (0 == func_num_args ()) {
            throw new Exception('enwork must fill param');
        }
        $queue_content = json_encode(func_get_args());
        $redis_handle = CXCache::instance('worker');
        $result = $redis_handle->rPush($queue_name, $queue_content);

        return $result;
    }

    public static function dework($queue_name = 'work') {
        $redis_handle = CXCache::instance('worker');
        $worker_content = $redis_handle->lPop($queue_name);
        if (empty($worker_content)) {
            return;
        }
        $debug_info = 'content:' . $worker_content;
        $worker_content = json_decode($worker_content, true);
        list($worker_class, $worker_method) = explode('.', array_shift($worker_content));
        try {
            require_once APPLICATION_PATH . '/workes/' . $worker_class . '.work.php';
            call_user_func_array(array(new $worker_class, $worker_method), $worker_content);
        }
        catch (Exception $e) {
            throw $e;
        }

        return $debug_info;
    }
}


/**
 * BaseCx
 */
abstract class CX {
    /**
     *
     * @param string $variable
     * @return object
     */
    public function  __get($variable) {
        switch ($variable) {
            case 'db':
                $variable = CXDB::instance();
                $this->db = $variable;
                break;
            case 'template':
                $variable = CXTemplate::instence();
                $this->template = $variable;
                break;
            case 'monitor':
                $variable = CXMonitor::instance();
                $this->monitor = $variable;
                break;
            case 'cdn':
                $variable = CXTool::getConfig('cdn_url');
                $variable = $variable[array_rand($variable)];
                $this->cdn = $variable;
                break;
            default:
                throw new Exception('get undefined property');
        }

        return $variable;
    }
}

/**
 * Controller
 */
abstract class Controller extends CX {
    /**
     * default controller method
     */
    public function common() {}
}


abstract class Module extends CX {
    
}

//end file
